<?php

/**
 * Class Middleware
 */
class Middleware
{
    /**
     * @var array
     */
    protected static $_instances = [];

    /**
     * @param $all_middlewares
     * @param string $method_name
     * @throws BadRequestException
     */
    public static function load($all_middlewares, string $method_name = 'process')
    {
        foreach ($all_middlewares as $app_name => $middlewares) {
            foreach ($middlewares as $class_name) {
                if (\method_exists($class_name, $method_name)) {
                    static::$_instances[$app_name][] = [new $class_name(), $method_name];
                } else {
                    // @todo Log
                    //echo "middleware {$class_name}::{$method_name} not exsits\n";
                    throw new BadRequestException("middleware {$class_name}::{$method_name} not exsits\n", 5);
                }
            }
        }
    }

    /**
     * @param $app_name
     * @param bool $with_global_middleware
     * @return array
     */
    public static function getMiddleware($app_name, bool $with_global_middleware = true): array
    {
        $global_middleware = $with_global_middleware && isset(static::$_instances['']) ? static::$_instances[''] : [];
        if ($app_name === '') {
            return \array_reverse($global_middleware);
        }
        $app_middleware = static::$_instances[$app_name] ?? [];
        return \array_reverse(\array_merge($global_middleware, $app_middleware));
    }

    /**
     * @param $app_name
     * @return bool
     */
    public static function hasMiddleware($app_name): bool
    {
        return isset(static::$_instances[$app_name]);
    }

    /**
     * @param $app
     * @param $call
     * @param null $args
     * @param bool $with_global_middleware
     * @return Closure|mixed
     * @throws BadRequestException
     */
    public static function getCallback($app, $call, $args = null, bool $with_global_middleware = true)
    {
        $args = $args === null ? null : \array_values($args);
        $middleware = static::getMiddleware($app, $with_global_middleware);
        if ($middleware) {
            $callback = \array_reduce($middleware, function ($carry, $pipe) {
                return function ($context) use ($carry, $pipe) {
                    return $pipe($context, $carry);
                };
            }, function ($context) use ($call, $args) {
                try {
                    if ($args === null) {
                        $response = $call($context);
                    } else {
                        $response = $call($context, ...$args);
                    }
                } catch (\Throwable $e) {
                    throw new BadRequestException($e->getMessage(), 1);
                }
                if (\is_scalar($response) || null === $response) {
                    $response = [];
                }
                return $response;
            });
        } else {
            if ($args === null) {
                $callback = $call;
            } else {
                $callback = function ($context) use ($call, $args) {
                    return $call($context, ...$args);
                };
            }
        }
        return $callback;
    }
}
